/* Copyright © 2010 LPRM
Universidade Federal do Espírito Santo - RNP-GT CWTools

This file is part of GW-Android.

GW-Android is free software: you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.

GW-Android is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Lesser General Public License for more details.

You should have received a copy of the GNU Lesser General Public License
along with GW-Android. If not, see <http://www.gnu.org/licenses/>. */
package br.ufes.cwtools.gw.android;

import java.io.File;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Queue;

import android.app.Activity;
import android.app.ProgressDialog;
import android.content.Context;
import android.content.DialogInterface;
import android.content.DialogInterface.OnCancelListener;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.net.Uri;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.os.RemoteException;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ArrayAdapter;
import android.widget.Button;
import android.widget.ListView;
import android.widget.TextView;
import br.ufes.cwtools.gw.android.components.Component;
import br.ufes.cwtools.gw.android.components.RequiredComponent;
import br.ufes.cwtools.gw.android.components.RequiredComponentsParser;
import br.ufes.cwtools.gw.android.connection_service.ConnectionServiceConnection;
import br.ufes.cwtools.gw.android.connection_service.IConnectionService;

public class ComponentDownloader extends Activity {
	private static final String LOG_TAG = "ComponentDownloader";
	private static int MAIN_ACTIVITY_REQUEST_CODE = 2011;

	private String mRepository;
	RequiredComponentsParser parser;
	List<RequiredComponent> required;
	private List<RequiredComponent> mMissingComponents = new ArrayList<RequiredComponent>();
	private ComponentListAdapter mComponentListAdapter;

	private Queue<String> mInstallQueue = new LinkedList<String>();
	private Map<String, RequiredComponent> mRequestsMap = new HashMap<String, RequiredComponent>();
	private Map<String, String> mPathsMap = new HashMap<String, String>();
	private Map<Integer, String> mRequestCode = new HashMap<Integer, String>();
	private int mNextRequestCode = 1;
	private boolean installing = false;

	private ProgressDialog downloadingDialog;
	private Button installAllButton;
	private boolean cancelled = true;
	
	private ConnectionServiceConnection mConnection;
	
	private Handler cdHandler;
	private int currentComponent;

	@Override
	protected void onDestroy() {
		super.onDestroy();
		
		if(mConnection != null){
			try {
				mConnection.unregisterAll();
				unbindService(mConnection);
			} catch (RemoteException e) {
				Log.e(LOG_TAG, "Erro na destruição da atividade.", e);
			}
		}
		if (downloadingDialog != null){
			downloadingDialog.dismiss();
		}
	}

	@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.component_download);

		String appXml = getIntent().getExtras().getString(Component.EXTRA_APPLICATION_XML);
		parser = new RequiredComponentsParser();
		parser.parse(appXml);

		mRepository = getIntent().getExtras().getString(Component.EXTRA_APPLICATION_URL) + parser.getRepository();
		required = parser.getRequiredComponents();
		refreshMissingComponents();

		// All components are already installed.
		if (mMissingComponents.size() == 0) {
			launchMainActivity();
			return;
		}

		mComponentListAdapter = new ComponentListAdapter(this, R.layout.component_download_item, mMissingComponents);
		ListView componentListView = (ListView) findViewById(R.id.listComponents);
		componentListView.setAdapter(mComponentListAdapter);
		componentListView.setClickable(false);
		componentListView.setItemsCanFocus(false);
		mComponentListAdapter.notifyDataSetChanged();

		installAllButton = (Button) findViewById(R.id.button);
		
		mConnection = new ConnectionServiceConnection(ComponentDownloader.class.getName()) {
			@Override
			public void onServiceConnected() { /*do nothing*/ }

			@Override
			public void onRequestFinished(String id, String filePath) {
				if (!cancelled) {
					mPathsMap.put(id, filePath);
					mInstallQueue.add(id);
					
					//Atualiza o dialog
					cdHandler.sendEmptyMessage(0);
					
					processQueue();
				} else {
					resetInterface();
				}
			}
		};
		
		currentComponent = -1; //No primeiro update ele vai pra 0
		cdHandler = new Handler(){
			@Override
			public void handleMessage(Message msg) {
				updateDownloadingDialog();
			}
		};

		bindService(new Intent(IConnectionService.class.getName()), mConnection, Context.BIND_AUTO_CREATE);
	}
	
	private void updateDownloadingDialog(){
		currentComponent++;
		RequiredComponent rc = mMissingComponents.get(currentComponent);
		downloadingDialog.setMessage("Baixando " + rc.getName() + "\n" + rc.getSize());
		downloadingDialog.show();
	}
	
	private void resetInterface(){
		downloadingDialog.dismiss();
		downloadingDialog = null;
		
		installAllButton.setText("Instalar todos componentes");
		installAllButton.setEnabled(true);
		
		mInstallQueue.clear();
		mRequestsMap.clear();
		mPathsMap.clear();
		
		currentComponent = -1;
		cancelled = true;
	}

	private void refreshMissingComponents() {
		List<String> installed = getInstalledComponentList();
		filterMissingComponents(required, installed);
	}

	public void installAllOnClick(View view) {
		cancelled = false;
		installAllButton.setText("Aguarde...");
		installAllButton.setEnabled(false);
		
		//Setup downloading dialog
		downloadingDialog = new ProgressDialog(this);
		downloadingDialog.setTitle("Download");
		downloadingDialog.setIndeterminate(true);
		downloadingDialog.setCancelable(true);
		downloadingDialog.setOnCancelListener(new OnCancelListener() {
			@Override
			public void onCancel(DialogInterface dialog) {
				resetInterface();
			}
		});
		
		//Atualiza o downloading dialog com a informação do primeiro componente
		updateDownloadingDialog();
		
		for(RequiredComponent c : mMissingComponents){
			installComponent(c);
		}
	}

	private List<String> getInstalledComponentList() {
		Intent componentSearchIntent = new Intent();
		componentSearchIntent.addCategory(Constants.COMPONENTS_INTENT_CATEGORY);
		componentSearchIntent.setAction(Constants.COMPONENTS_INTENT_ACTION_DEFAULT);
		List<ResolveInfo> ril = getPackageManager().queryIntentActivities(componentSearchIntent, PackageManager.MATCH_DEFAULT_ONLY);
		List<String> componentList = new ArrayList<String>();
		Log.d(LOG_TAG, "Search for installed components found " + ril.size() + " matches.");
		for (ResolveInfo ri : ril) {
			if (ri.activityInfo != null) {
				componentList.add(ri.activityInfo.packageName);
				Log.d(LOG_TAG, "Found installed: " + componentList.get(componentList.size() - 1));
			}
		}
		return componentList;
	}

	private void filterMissingComponents(List<RequiredComponent> required, List<String> installed) {
		HashSet<RequiredComponent> notInstalled = new HashSet<RequiredComponent>();
		for (RequiredComponent rc : required) {
			Log.d(LOG_TAG, "Componente requirido: " + rc.getName());
			if (!installed.contains(rc.getPackageName())) {
				Log.d(LOG_TAG, "Componente não instalado, adicionando na fila: " + rc.getName());
				notInstalled.add(rc);
			}
		}

		ArrayList<RequiredComponent> requiredComponentList = new ArrayList<RequiredComponent>(notInstalled);
		Collections.sort(requiredComponentList);

		mMissingComponents.clear();
		mMissingComponents.addAll(notInstalled);
		Collections.sort(mMissingComponents);
	}

	private String getUrl(RequiredComponent rc) {
		return mRepository + "/" + rc.getMd5Hash() + "/downloadByMd5hash";
	}

	private void installComponent(RequiredComponent rc) {
		Log.d(LOG_TAG, "Installing component " + rc.getName());

		try {
			String id = ConnectionServiceConnection.generateId();
			mRequestsMap.put(id, rc);
			mConnection.registerGetRequest(getUrl(rc), id, true);
		} catch (RemoteException e) {
			Log.e(LOG_TAG, "FUUUUUUUUUUUU! DEBUG ME PLEASE");
		}
	}

	private void processQueue() {
			if (!installing && mInstallQueue.size() != 0){
				String id = mInstallQueue.poll();
	
				//Instala o componente baixado.
				installing = true;
				Intent intent = new Intent(Intent.ACTION_VIEW);
				intent.setDataAndType(Uri.fromFile(new File(mPathsMap.get(id))), "application/vnd.android.package-archive");
				
				int requestCode = mNextRequestCode++;
				mRequestCode.put(requestCode, id);
				startActivityForResult(intent, requestCode);
			}
	}

	@Override
	protected void onActivityResult(int requestCode, int resultCode, Intent data) {
		super.onActivityResult(requestCode, resultCode, data);
		
		Log.d(LOG_TAG, "onActivityResult(requestCode = " + requestCode + ", resultCode = " + resultCode + ", data)");
		
		installing = false;
		
		//Reconhece um "back" da MainActivity
		if (requestCode == MAIN_ACTIVITY_REQUEST_CODE) {
			finish(); // Volta pra tela de login
			return;
		}

		String id = mRequestCode.get(requestCode);

		refreshMissingComponents();
		mComponentListAdapter.notifyDataSetChanged();

		RequiredComponent rc = mRequestsMap.get(id);
		// If it still contains the component, installation was cancelled.
		// The resultCode from Android package installer is not reliable
		if (mMissingComponents.contains(rc)) {
			resetInterface();
			return;
		}

		mRequestsMap.remove(id);
		mPathsMap.remove(id);

		if (mMissingComponents.size() == 0) {
			launchMainActivity();
		} else {
			processQueue();
		}
	}

	private class ComponentListAdapter extends ArrayAdapter<RequiredComponent> {
		private List<RequiredComponent> mComponents;

		public ComponentListAdapter(Context context, int textViewResourceId,
				List<RequiredComponent> objects) {
			super(context, textViewResourceId, objects);
			mComponents = objects;
			if (mComponents == null) {
				mComponents = new ArrayList<RequiredComponent>();
			}
		}

		@Override
		public View getView(int position, View convertView, ViewGroup parent) {
			View v = convertView;
			if (v == null) {
				LayoutInflater vi = (LayoutInflater) getSystemService(Context.LAYOUT_INFLATER_SERVICE);
				v = vi.inflate(R.layout.component_download_item, null);
			}

			RequiredComponent rc = mComponents.get(position);
			((TextView) v.findViewById(R.id.componentName)).setText(rc
					.getName());
			((TextView) v.findViewById(R.id.componentVersion)).setText(" v"
					+ rc.getVersion());
			((TextView) v.findViewById(R.id.componentSize)).setText(rc
					.getSize());
			((TextView) v.findViewById(R.id.componentDescription)).setText(rc
					.getDescription());
			v.setTag(rc);

			return v;
		}
	}

	private void launchMainActivity() {
		Intent intent = new Intent(this, MainActivity.class);
		intent.putExtras(getIntent().getExtras());
		startActivityForResult(intent, MAIN_ACTIVITY_REQUEST_CODE);
	}
}